By: Hu Wanqing
, Jiang Chunhui
, Hou Denghao
, Leon Chow WenHao
Since: August 2017
- 1. Welcome to Planno
- 2. Setting up
- 3. Design
- 4. Implementation
- 4.1. Undo/Redo mechanism
- 4.2. Find mechanism
- 4.3. Sort mechanism
- 4.4. Event Model
- 4.5. AddEvent mechanism
- 4.6. ListEvent mechanism
- 4.7. DeleteEvent mechanism
- 4.8. EditEvent mechanism
- 4.9. ShowParticipants mechanism
- 4.10. ShowAllJoinedEvent mechanism
- 4.11. Person-Event interaction
- 4.12. Portrait Command mechanism
- 4.13. Toggle mechanism
- 4.14. FindEvent mechanism
- 4.15. Sort Event mechanism
- 4.16. Logging
- 4.17. Configuration
- 5. Documentation
- 6. Testing
- 7. Dev ops
- Appendix A: User stories
- Appendix B: Use cases
- Appendix C: Non functional requirements
- Appendix D: Glossary
Planno is a Java application that helps users to conveniently manage contact as well as event details. To achieve this, Planno provides a large number of simple commands for users.
There are many ways to contribute to Planno: coding, testing, improving the build process and tools, or contributing to the documentation. This guide provides information that will not only help you get started as a Planno contributor, but that you’ll find it useful to refer to even if you are already an experienced contributor.
This developer guide aims to give future programmers who hope to modify this application a general description of the architecture and design of the Desktop Application Planno. Planno provides users a platform to store and manage people’s contact information, and events related to people in Planno. This developer guide consists of the following sections:
- 1. Welcome to Planno
- 2. Setting up
- 3. Design
- 4. Implementation
- 4.1. Undo/Redo mechanism
- 4.2. Find mechanism
- 4.3. Sort mechanism
- 4.4. Event Model
- 4.5. AddEvent mechanism
- 4.6. ListEvent mechanism
- 4.7. DeleteEvent mechanism
- 4.8. EditEvent mechanism
- 4.9. ShowParticipants mechanism
- 4.10. ShowAllJoinedEvent mechanism
- 4.11. Person-Event interaction
- 4.12. Portrait Command mechanism
- 4.13. Toggle mechanism
- 4.14. FindEvent mechanism
- 4.15. Sort Event mechanism
- 4.16. Logging
- 4.17. Configuration
- 5. Documentation
- 6. Testing
- 7. Dev ops
- Appendix A: User stories
- Appendix B: Use cases
- Appendix C: Non functional requirements
- Appendix D: Glossary
-
JDK
1.8.0_60
or laterℹ️Having any Java 8 version is not enough.
This app will not work with earlier versions of Java 8. -
IntelliJ IDE
ℹ️IntelliJ by default has Gradle and JavaFx plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
To set up the project in your computer, please follow the following steps:
-
Fork this repo, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
The above steps will help you generate all resources required by the application and tests.
-
Run the
seedu.address.MainApp
and try a few commands -
Run the tests to ensure they all pass.
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
After forking the repo, links in the documentation will still point to the se-edu/addressbook-level4
repo. If you plan to develop this as a separate product (i.e. instead of contributing to the se-edu/addressbook-level4
) , you should replace the URL in the variable repoURL
in DeveloperGuide.adoc
and UserGuide.adoc
with the URL of your fork.
Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.
Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).
ℹ️
|
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based) |
When you are ready to start coding,
-
Get some sense of the overall design by reading the Architecture section.
-
Take a look at the section Suggested Programming Tasks to Get Started.
Figure 3.1.1: Architecture Diagram
The Architecture Diagram (Figure 3.1.1) given above explains the high-level design of the App. Given below is a quick overview of each component:
💡
|
The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, you can modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture .
|
Main
has only one class called MainApp
. It is responsible for:
-
(At app launch) Initializing the components in the correct sequence, and connects them up with each other.
-
(At app shut down) Shutting down the components and invokes cleanup method where necessary.
Commons
represents a collection of classes used by other components. Among them, the following two classes play important roles at the architecture level:
-
EventsCenter
: This class is written using Google’s Event Bus library. Components communicate with each other by posting event in this class (i.e. a form of event-driven nature of design). -
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four major components:
Each of the four components above
-
Defines its API in an
interface
with the same name as the Component. -
Exposes its functionality using a
{Component Name}Manager
class.
For example, the Logic
component defines it’s API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class.
The Sequence Diagram (Figure 3.1.2) below shows how the components interact using EventsCenter
for the scenario where the user issues the command delete 1
.
Figure 3.1.2: Component interactions for delete 1
command (part 1)
ℹ️
|
The Model simply raises a AddressBookChangedEvent when the Address Book data are changed, instead of asking the Storage to save the updates to the hard disk.
|
The diagram below (Figure 3.1.3) shows how the EventsCenter
reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.
Figure 3.1.3: Component interactions for delete 1
command (part 2)
ℹ️
|
The event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.
|
The sections below give you more details of each component.
Figure 3.2.1: Structure of the UI Component
API : Ui.java
The structure of the UI
Component is shown in the Class Diagram (Figure 2.2.1) above. The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
, BrowserPanel
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
The UI
component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component:
-
Executes user commands using the
Logic
component. -
Binds itself to some data in the
Model
so that the UI can auto-update when data in theModel
changes. -
Responds to events raised from various parts of the App and updates the UI accordingly.
The diagrams (Figure 3.2.2.1 to Figure 3.2.2.3) given below show the structure of whole logic component, and structure of commands in details.
Figure 3.2.2.1: Structure of the Logic Component
Figure 3.2.2.2: Structure of Commands in the Logic Component. This diagram shows finer details concerning XYZCommand
and Command
in Figure 3.2.2.1
API :
Logic.java
Figure 3.2.2.1 shows the structure of the Logic
component. The LogicManager
firstly calls the AddressBookParser
to parse the user input.
This results in a Command
object which is executed by the LogicManager
. Then, the command execution can affect the Model
(e.g. adding a person) and/or raise events. The result of the command execution is encapsulated as a CommandResult
object which is passed back to the Ui
.
Given below is the Sequence Diagram (Figure 3.2.2.3) for interactions within the Logic
component for the execute("delete 1")
API call.
Figure 3.2.2.3: Interactions Inside the Logic Component for the delete 1
Command
The following diagram (Figure 3.2.3) shows the class structure of the Model component.
Figure 3.2.3: Structure of the Model Component
API : Model.java
Generally, the Model
is managed by a Model manager, which
-
stores a
UserPref
object that represents the user’s preferences. -
maintains an
AddressBook
and aEventList
. -
stores 2 unmodifiable list:
ObservableList<ReadOnlyPerson>
andObservableList<ReadOnlyEvent>
. They are bounded to UI so that the UI can automatically updates when the data in the list change. -
does not depend on any of the other three components.
In detail, the AddressBook
and the EventList
are respectively responsible for person and event information.
-
The
AddressBook
-
stores people’s information as a person list with no duplicate persons. The information includes one’s personal information and contact details.
-
keeps track of all the tags that had been added to some people in the person list
-
for each person in the list, the person holds a modifiable tag list that contains all the tag this person has.
-
-
The
EventList
-
stores event’s information as an event list.
-
for each event in the list, the event maintains a modifiable list to keep track of who the participants of the events are.
-
Figure 3.2.4: Structure of the Storage Component
API : Storage.java
The diagram (Figure 3.2.4) above shows the structure of the Storage
component.
The StorageManager
handles the saving and loading of data for both AddressBookStorage
and EventStorage
.
XmlSerializableAddressBook
and XmlSerializableEventStorage
handle the conversion from Java to Xml format using XmlAdaptedPerson
, XmlAdaptedEvent
and XmlAdaptedTag
.
The Storage
component:
-
Saves
UserPref
objects in json format and reads it back. -
Saves the Address Book data in xml format and reads it back.
-
Saves event storage data in xml format and reads it back.
This section describes some noteworthy details on how certain features are implemented. For the features described in this section, their design considerations are included where applicable.
This feature helps the user to undo / redo some commands when they find that they have made some mistakes.
The undo/redo mechanism is facilitated by an UndoRedoStack
, which resides inside LogicManager
. It supports undoing and redoing of commands that modifies the state of the address book (e.g. add
, edit
). Such commands will inherit from UndoableCommand
.
UndoRedoStack
only deals with UndoableCommands
. Commands that cannot be undone will inherit from Command
instead. The following diagram (Figure 3.1.1.1) shows the inheritance diagram for commands:
Figure 4.1.1.1: Structure of commands
As you can see from the diagram (Figure 4.1.1.1), UndoableCommand
adds an extra layer between the abstract Command
class and concrete commands that can be undone, such as the DeleteCommand
. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of Planno before execution. UndoableCommand
contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.
Commands that are not undoable are implemented this way:
public class ListCommand extends Command {
@Override
public CommandResult execute() {
// ... list logic ...
}
}
With the extra layer, the commands that are undoable are implemented this way:
public abstract class UndoableCommand extends Command {
@Override
public CommandResult execute() {
executeUndoableCommand();
}
protected abstract void undo();
protected abstract void redo();
}
[source,java]
public class DeleteCommand extends UndoableCommand {
@Override
public CommandResult executeUndoableCommand() {
// ... delete logic ...
}
@Override
protected void undo() {
// ... undo deleting logic ...
}
@Override
protected void redo() {
// ... redo deleting logic ...
}
}
As you can see, different types of UndoableCommands
have different implementations of undo/redo, which will be included
from section 3.1.2 to 3.1.5. Now we can look at an example of how UndoRedoStack
works:
Suppose that the user has just launched the application. The UndoRedoStack
will be empty at the beginning.
The user executes a new UndoableCommand
, delete 5
, to delete the 5th person in the address book.
The personToDelete
(which is represented as $5
) will be saved inside this delete 5
command for subsequent undo command.
The delete 5
command will then be pushed to the undoStack
(the current state is saved together with the command).
This is shown in the image (Figure 4.1.1.2) below.
Figure 4.1.1.2: undo example part 1
As the user continues to use the program, more commands are added into the undoStack
.
For example, the user may execute add n/David …
to add a new person.
This person (which is represented as $David) will be saved in this add command for subsequent undo command.
This is show in the image (Figure 4.1.1.3) below.
Figure 4.1.1.3: undo example part 2
ℹ️
|
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.
|
The user now decides that adding the person $David
was a mistake, and decides to undo that action using undo
.
We will pop the most recent command out of the undoStack
and push it back to the redoStack
.
We will execute the undo()
method inside that command.
This is shown in the image (Figure 4.1.1.4) below.
Figure 4.1.1.4: undo example part 3
ℹ️
|
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack .
|
The following sequence diagram (Figure 4.1.1.5) shows how the undo operation works:
Figure 4.1.1.5: undo sequential diagram
The redo does the exact opposite (pops from redoStack
, push to undoStack
,
and execute the redo()
method inside that method).
ℹ️
|
If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack .
|
The user now decides to execute a new command, clear
. As before, clear
will be pushed into the undoStack
.
This time the redoStack
is no longer empty. It will be purged as it no longer make sense to redo the add n/David
command
(this is the behavior that most modern desktop applications follow). This is shown in the image (Figure 3.1.1.6) below.
Figure 4.1.1.6: redo example part 1
Commands that are not undoable are not added into the undoStack
. For example, list
, which inherits from Command
rather than UndoableCommand
, will not be added after execution. This is shown in the image (Figure 4.1.1.7) below.
Figure 4.1.1.7: redo example part 2
The following activity diagram (Figure 4.1.1.8) summarize what happens inside the UndoRedoStack
when a user executes a new command.
Figure 4.1.1.8: undo/redo activity
Aspect: Implementation of UndoableCommand
Alternative 1 (current choice): Add a new abstract method executeUndoableCommand()
Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with Command
do not have to know that executeUndoableCommand()
exist.
Cons: It might be hard for new developers to understand the template pattern.
Alternative 2: Just override execute()
Pros: This implementation does not involve the template pattern, and is easier for new developers to understand.
Cons: Classes that inherit from UndoableCommand
must remember to call super.execute()
, or lose the ability to undo/redo.
Aspect: How undo & redo executes
Alternative 1 (current choice): Implement undo/redo for each undoable command separately
Pros: The app will use less memory (e.g. for delete
, just save the person being deleted).
Cons: We must ensure that the implementation of each individual command are correct.
Alternative 2: Save the entire address book.
Pros: This is easy to implement.
Cons: This may cause performance issues in terms of memory usage.
Aspect: Type of commands that can be undone/redone
Alternative 1 (current choice): Only include commands that modifies the address book (add
, clear
, edit
)
Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are lost).
Cons: User might think that undo also applies when the list is modified (undoing filtering for example), only to realize that it does not do that, after executing undo
.
Alternative 2: Include all commands
Pros: This could be more intuitive for users.
Cons: User have no way of skipping such commands if he or she just want to reset the state of the address book and not the view.
Additional Info: See our discussion here.
Aspect: Data structure to support the undo/redo commands
Alternative 1 (current choice): Use separate stack for undo and redo
Pros: New incoming developers of our project, such as new Computer Science undergraduates are easy to understand
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both HistoryManager
and UndoRedoStack
.
Alternative 2: Use HistoryManager
for undo/redo
Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase.
Cons: We should deal with commands that have already been undone: We must remember to skip these commands. This violates Single Responsibility Principle and Separation of Concerns, as HistoryManager now needs to do two different things
The following sections provide the specific implementations of undo and redo for some types of UndoableCommand
:
We save the personToAdd
for subsequent undoing when we undo add command.
Before adding this person, we identify all tags which are attached personToAdd
, but are not in the tag list.
In other words, we will extract tags which attach to personToAdd
only, and save then in another list: newTags
.
ℹ️
|
There is A list of tags attaching personToAdd . When we undo, we need to eliminate those tags that attach to this person only. |
When we undo it, we firstly remove those tags in newTags
, and then delete this person from address book.
When we redo it, we will add this person into address book again.
The AddEventCommand
(addE) has similar undo/redo implementation to this.
Aspect: How to deal with tags
Alternative 1 (current choice): Remove all tags which only exists in this person
Pros: This allows the add command to be undone completely.
Cons: We need to ensure each tag that is supposed to be removed does not exist in any other person.
Alternative 2: Not remove any tag
Pros: This is easy to implement.
Cons: Tag list is not reverted to the state completely before add command executes, which may confuse users.
We save the personToDelete
for subsequent undoing when we execute a delete command.
When we undo it, we add personToDelete
back to the address book at its original position before deletion.
When we redo it, we simply delete this person.
The DeleteEventCommand
(deleteE) has similar undo/redo implementation to this.
Aspect: Where to add the target person when we undo the delete person
Alternative 1 (current choice): Add it to the original position index
Pros: This allows the delete command to be undone completely, and the sequence of persons in address book will not change because of the undo.
Cons: Time complexity will increase, because it requires all persons behind index
to switch to right.
Alternative 2: Just add it at the back of address book
Pros: This is easy to implement, and is more efficient in terms of time complexity.
Cons: The sequence of persons in address book will change, which may confuse users.
We save both the personToEdit
and the editedPerson
for subsequent undoing when we execute an edit / portrait command.
Similar to add
command, we need to save the tags which only attach to editedPerson
in a list newTags
.
When we undo it, we will firstly remove tags in newTags
, and then modify editedPerson
to be personToEdit
.
When we redo it, we will simply modify personToEdit
to be editedPerson
.
The EditEventCommand
(editE) has similar undo/redo implementation to this.
We save both targetPerson
and targetEvent
for subsequent undoing when we execute a join / disjoin command.
When we undo it, we will disjoin/ join this person and this event, respectively.
When we redo it, we will join/ disjoin back this person and this event, respectively.
We need to save the current state of address book
and event list
for subsequent undoing when we execute a clear command.
When we undo it, we will restore the address book
and event list
to the state before the clear command executed.
When we redo it, we just clear everything again.
The Find mechanism is facilitated by NameContainsKeyWordPredicate
class, which resides in model.person
package. This command
allows users to find a list of persons by tag and name keywords. A person that has at least one of the keywords will be selected.
Such command will inherit from Command
.
The find command accepts two types of keywords:
-
tag
: it is identified by a prefixt/
.
e.g.find t/friends
means to find any person that have a tag calledfriends
-
name
: anything that does not begin withtag
is identified asname
keyword.
e.g.find friends
means to find any person whose name contains the keywordfriends
ℹ️if a tag name
is not preceded with a prefixt/
, then thetag name
will be identified as aperson name
. As a result, the people with their name containing thetag name
will be displayed.
The mechanism for NameContainsKeywordPredicate
to select people is implemented this way:
@Override
public boolean test(ReadOnlyPerson person) {
boolean isSelected = keywords.stream().anyMatch (keyword –>
StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
// check whether this person’s name contains any of the name key word
If (isSelected == true) {
return isSelected;
}
for (String keyword : keywords) {
If (keyword.length() >= 2 && keyword.substring(0, 2).equals(“t/”) {
String tagName = “[” + keyword.substring(2) + “]”;
for(Tag tag : person.getTags()) {
if (tag.toString().equals(tagName)) {
isSelected = true;
}}}
return isSelected;
}
Below is a sequence diagram (Figure 4.2) for executing a find command: find t/friends
. It will find persons that have the tag friends
.
Figure 4.2: sequence diagram for find command
The command will be sent to LogicManager
, and LogicManager
will call AddressBookParser
to parse
the command. Subsequently, FindCommandParser
will parse the argument t/friends
and create a new findCommand
with
predicate t/friends
. Then LogicManager
will execute findCommand
to update the filtered person list with predicate friends
.
This update will notify GUI to update the filtered person list,
so that the persons with the tag friends
is displayed.
Aspect: Implementation of find command
Alternative 1 (current choice): Select people that have any of the keywords
Pros: We only need to ensure that at least one keyword exists for every person.
Cons: It is difficult to know by which keyword a person is selected.
Alternative 2: Select people that has any of the keywords, and highlight selected keywords in each person’s person card
Pros: Shows clearly what keywords each selected person contains.
Cons: We need to go through everything of a person, in order to highlight EVERY keyword the person has.
Aspect: Types of keywords that can be found
Alternative 1 (current choice): Only person name and tag can be used in the find command
Pros: We only need to check a person’s name and tag list to find any matches.
Cons: User will not be able to find a person by other information such as phone or email.
Alternative 2: we allow every information of a person (i.e. phone, email, address) to be used as keywords in the find command.
Pros: A person can be found in many ways.
Cons: Implementation is complicated.
The sort command is facilitated by the LogicManager
class. It supports sorting contact list and makes it easier for users to find contacts according to alphabetical order of their names.
The following sequence diagram (Figure 4.3) shows how the sort command works:
Figure 4.3: sequence diagram for sort command
When user enters a sort command, it will be received by LogicManager
.Then, LogicManager
calls AddressBookParser
to parse the command. And AddressBookParser
will create SortCommand
command object and returns it.When LogicManager
receives the command object, it will execute it.
The SortCommand
object calls sortPersons()
in the `Model`as follow:
public class SortCommand extends Command {
public static final String COMMAND_WORD = "sort";
public static final String MESSAGE_SUCCESS = "Sorted all persons";
@Override
public CommandResult execute() {
model.sortPersons();
return new CommandResult(MESSAGE_SUCCESS);
}
}
And This is how Model
update the person list using sortPersons()
:
public synchronized void sortPersons() {
addressBook.sortPersons();
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
In the person list, we will sort contacts according to alphabetical order of their names:
public void sort() {
Collections.sort(internalList, new Comparator<Person>() {
public int compare (Person p1, Person p2) {
return p1.getName().toString().compareToIgnoreCase(p2.getName().toString()); } });
}
After sorting the person list, SortCommand
will create CommandResult
object and return it. And LogicManager
receives CommandResult
and shows related message through UI
.
Aspect: How sort command affects the data in the person list
Alternative 1 (current choice): Sort the copy of contact list and return it
Pros: It is more defensive and keeps the data unchanged.
Cons: Users have to sort the list every time when they open the application.
Alternative 2: Sort the actual contact list and return it
Pros: Users do not need to type the command every time.
Cons: Sort command is not undoable which means that the order of list cannot change anymore after you call it. It is also less defensive because you can frequently change the original data.
Aspect: How sort command executes for the person list+
Alternative 1 (current choice): Sort the contact list according to alphabetical order (ascending order)
Pros: It is easy to implement.
Cons: Users do not have other choices to sort the person list.
Alternative 2: Sort the contact list according to different order (ascending order or descending order)
Pros: Users will have more choices.
Cons: It will be more complex for developers to maintain and test.
Aspect: What can be sorted in the person list
Alternative 1 (current choice): Sort contacts according to their names
Pros: It is useful and necessary for general users.
Cons: Users do not have other choices to sort the person list.
Alternative 2: Sort contacts according to their addresses/emails/phone numbers
Pros: It provides more choices for users.
Cons: Because address/email/phone number is unique, you can usually find them without sorting the list.
To support the user managing different events, we create an event model. The Figure 4.4.1 shows what attributes include in the event
Figure 4.4.1 event class
To store a brunches of events, we use the EventList
class. Here we apply n-Tier architectural style.
Inside the EventList
, there is a UniqueEventList
, where all Events
in it should be different.
This is shown in Figure 4.4.2
Figure 4.4.2 n-Tier style of EventList
Note that the EventList
is separate from AddressBook
. This can reduce coupling between Person
and Event
,
and satisfies "Separation of Concerns Principle" .
Inside the EventList
, we add the add
, delete
, edit
commands to manage the events.
Aspect: How to implement EventList
+
Alternative 1 (current choice): Make it as a separate class from AddressBook
Pros: This reduces coupling between Person
and Event
, and make it easier to manage Event
.
Cons: It requires a lot of work to implement the whole system.
Alternative 2: Put it as an attribute inside AddressBook
Pros: This is easy to implement.
Cons: This violates SRP, where AddressBook need to manage both Person and Event.
The add event command is facilitated by LogicManager
class. It allows user to add a new event to the event list.
The following sequence diagram (Figure 4.5) shows how add event command works:
Figure 4.5: AddEvent command sequence diagram
The user enters an addEvent command, and the command is received by LogicManager
. Then LogicManager
calls AddressBookParser
to parse the command.
AddressBookParser
will first check the format of the parameters. If the format is valid, it constructs a new AddEventCommand
object.
Logic Manager
then executes AddEventCommand
to add the event into Model
.
Then Logic Manager
will return the command result generated by AddEventCommand
to UI
.
The list event command is facilitated by the LogicManager
class. It supports listing all the events for users to process.
The following sequence diagram (Figure 4.6) shows how the list event command works:
Figure 4.6: sequence diagram for list command
When user enters a listE command, it will be received by LogicManager
. The LogicManager
calls AddressBookParser
to parse the command. Then, AddressBookParser
creates the ListEventCommand
object and returns it. When LogicManager
receives ListEventCommand
, it will execute the command.
When ListEventCommand
is executed, it will call updateFilteredEventList()
method in the Model
as follows:
public class ListEventCommand extends Command {
public static final String COMMAND_WORD = "listE";
public static final String MESSAGE_SUCCESS = "Listed all events";
@Override
public CommandResult execute() {
model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
return new CommandResult(MESSAGE_SUCCESS);
}
}
After Model
updates filtered event list which will be shown in the GUI, ListEventCommand
will create CommandResult
object and returns it. And LogicManager
receives CommandResult
and shows related message through UI
.
The delete event command is facilitated by the LogicManager
. It supports undo
and redo
as it inherits from UndoableCommand
. Other delete commands are implemented similarly.
The following sequence diagram (Figure 4.7) below shows how the delete event operation works:
Figure 4.7: sequence diagram for deleteE command
The user enters a deleteE command which is received by LogicManger
.
LogicManager
calls AddressBookParser
to parse the user command.
AddressBookParser
creates an DeleteEventCommand
object and returns it.
LogicManager
receives the Command
object and executes it.
The DeleteEventCommand
calls deleteEvent()
in the Model
.
Model
will proceed to delete the event.
The DeleteEventCommand
then creates a CommandResult
object and returns it to LogicManager
.
LogicManager
receives the result and displays it through the UI
.
Aspect: Implementation of DeleteEventCommand
Alternative 1 (current choice): Create a new command to delete events
Pros: It is responsible only for deleting events, and not people. Easier to implement.
Cons: Users may accidentally type in "delete" for deleting a person instead of "deleteE" for deleting an event.
Alternative 2: Modify DeleteCommand
to handle deletion for both persons and events
Pros: Users would be less likely to type the wrong command.
Cons: Any changes to the deletion process of either a person or event may affect the other. Harder to implement.
Aspect: How deleteE command executes
Alternative 1 (current choice): Deletes the event at the specified index
Pros: Easy to imlement.
Cons: User has to list/sort event list first.
Alternative 2: Delete the event with the specified name
Pros: Users can delete directly without having to get the index.
Cons: Users would have to type more if the event name is long. It will be more complex for developers to maintain and test.
The EditEvent Command
is facilitated by the LogicManager
.
It supports undo
and redo
as it inherits from UndoableCommand
.
The Logic manager will firstly call the parser to parse the user input. The parser will generate a descriptor which
contains information of edited attributes.
Instead of accessing into the Event inside Event List and modify its attributes,
EditEvent will create an event with certain attributes modified by descriptor. Then it simply replaces the original event
in the Event List.
The following code shows how it works:
@Override
protected CommandResult executeUndoableCommand() {
// logic of identifying target event
Event editedEvent = descriptor.build();
model.update(targetEvent, editedEvent);
}
Note that the update(targetEvent, editedEvent) method only replace targetEvent by editedEvent.
The show participants mechanism is facilitated by PersonJoinsEventsPredicate
, which resides in model.person
package. This command will help users to find participants of an event. For achieving this function, PersonJoinsEventsPredicate
will filter the person list. This command inherits from Command
.
The following sequence diagram (Figure 4.9) for executing a show participants command: showP 3
. It will show all the participants of the third event in the current list.:
Figure 4.9: sequence diagram for showP INDEX command
When user enters a showP 3
command, it will be received by LogicManager
. And LogicManager
calls AddressBookParser
to parse the command. Then AddressBookParser
will create ShowParticipantsCommandParser
to parse 3
.
After ShowParticipantsCommandParser
parses the index 3
, it will create ShowParticipantsCommand
object with this index as follows:
public ShowParticipantsCommand parse(String args) throws ParseException {
try {
Index index = ParserUtil.parseIndex(args);
return new ShowParticipantsCommand(index);
} catch (IllegalValueException ive) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowParticipantsCommand.MESSAGE_USAGE));
}
}
Then AddressBookParser
receives the command object and returns it to LogicManager
. When LogicManager
receives ShowParticipantsCommand
object, it will execute it.
ShowParticipantsCommand
will create the PersonJoinsEventsPredicate
which is p
in the diagram using the index given and use this predicate to update the person list as follows:
@Override
public CommandResult execute() throws CommandException {
List<ReadOnlyEvent> lastShownList = model.getFilteredEventList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
}
eventToShow = lastShownList.get(targetIndex.getZeroBased());
String name = eventToShow.getEventName().fullEventName;
PersonJoinsEventsPredicate predicate = new PersonJoinsEventsPredicate(name);
model.updateFilteredPersonList(predicate);
return new CommandResult(String.format(MESSAGE_SHOW_PARTICIPANTS_SUCCESS, eventToShow.getEventName()));
}
And that is how PersonJoinsEventsPredicate
filters the person list:
@Override
public boolean test(ReadOnlyPerson person) {
Boolean isSelected = false;
if (!person.getParticipation().isEmpty()) {
for (ReadOnlyEvent event: person.getParticipation()) {
if (!isSelected) {
isSelected = keywords.equals(event.getEventName().fullEventName);
}
}
}
return isSelected;
}
After Model
updates the person list which will be shown in the GUI, the ShowParticipantsCommand
object will create CommandResult
object and returns it. And LogicManager
receives CommandResult
and shows related message through UI
.
Aspect: How show participants command executes
Alternative 1 (current choice): Show participants of the event at the specified index
Pros: It is easy to implement.
Cons: Sometimes users have to list all the events so that they can get all the indexes.
Alternative 2: Show participants of the event with the specified name
Pros: Users can use the command without listing events.
Cons: Users need more time to type if the event name is too long.
The selectE command is facilitated by the LogicManager
. This command inherits from Command
.
The following sequence diagram (Figure 4.8) shows how the show all joined events operation works:
Figure 4.10: sequence diagram for selectE command
As seen in the sequence diagram (Figure 4.10) above, when the user enters a selectE command, the input is parsed by the SelectJoinedEventsCommandParser
.
A SelectJoinedEventsCommand
is then created from the parsed user input.
When the LogicManager
executes the SelectJoinedEventsCommand
, the latter calls getFilteredPersonList() to get the current person list.
The SelectJoinedEventsCommand
will then check the entered index values and get the names of the events the person/s have participated.
This is shown in the code snippet below:
public class SelectJoinedEventsCommand extends Command {
@Override
public CommandResult execute() throws CommandException {
for (Index targetIndex: indexList) {
if (targetIndex.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
personNames.append(lastShownList.get(targetIndex.getZeroBased()).getName()).append(", ");
for (ReadOnlyEvent event: lastShownList.get(targetIndex.getZeroBased()).getParticipation()) {
eventNames.append(event.getEventName()).append("[-]");
}
}
}
}
As seen in the code snippet above, the SelectJoinedEventsCommand
will throw an exception if the entered index values are invalid and will combine the participated event names into a String named "eventNames".
The SelectJoinedEventsCommand
will then separate the event names using the combination of symbols, "[-]", so they can be used by a predicate to find the participated events.
This is shown in the code snippet below:
public class SelectJoinedEventsCommand extends Command {
@Override
public CommandResult execute() throws CommandException {
String[] eventNameKeywords = (eventNames.toString().trim()).split("\\[-]+");
EventContainsKeywordPredicate predicate = new EventContainsKeywordPredicate(Arrays.asList(eventNameKeywords));
// Update the UI and return result.
}
}
As shown in the code snippet above, the event names will be separated and stored in a String[] named "eventNameKeywords".
A EventContainsKeywordPredicate
will be created and will use the "eventNameKeywords" to find the events that are participated by the entered person/s.
The UI will then be updated to display the events found.
Aspect: How selectE command executes
Alternative 1 (current choice): Shows events joined by person/s at the selected indexes
Pros: User does not have to type out names.
Cons: User has to list/sort person list first.
Alternative 2: Show events joined by person/s using user entered name
Pros: User can directly enter names to search for events where a person with the entered name has joined.
Cons: Users would have to type more if the name is long. User may mistype the name.
Events and persons can be connected (i.e.joined) if a person participates in an event. The connected relationship can also be disjoined. We will explain the implementation of the relationship in terms of Model and Storage.
For model, we simply use a "referencing model" to show the relationship between person and event, as shown in the following diagram.
Figure 4.11.1: interaction model
There is a participant list in the Event model, which stores all the persons who are involved in this event.
Similarly, the participation list in Person model stores all the event in which this person joins.
Hence, we need to maintain both lists when operating join
and disjoin
commands.
In storage, we cannot use the referencing model
which is used in the Model
component. This is because that it is easy to have an Infinity loop of reference. As shown in the diagram (Figure 4.11.2) below,
an event A references a person X, and X references another event B, and B continues to reference another person Y….As a result, the referencing list will be incredibly long:
Figure 4.11.2: infinity loop
To solve the infinity loop reference, we create two storage entities: XmlAdaptedEventNoParticipant
and XmlAdaptedPersonNoParticipation
for referencing purposes. Both of them do not have participant or participation information.
Hence, we can avoid the infinity referencing problem.
The updated storage is shown in the diagram (Figure 4.11.3) below:
Figure 4.11.3: interaction storage
Aspect: How to implement person-event interaction
Alternative 1 (current choice): Set participationList
in Person, and participantList
in Event. They reference each other
Pros: This is easy to implement, and fulfill what we need.
Cons: We need to maintain both lists when operating join
and disjoin
.
Alternative 2: Use association class
Pros: This allows us to save every participation entry.
Cons: We need to implement extra storage for association class, and it is costly in terms of time to operate show person
and select event
.
Disconnecting a person to an event: disjoin
This command will call ModelManager
's quitEvent() method to disconnect the person and the event. The following code segment from ModelManager
shows how quitEvent() works:
public void disjoin(Person personToRemove, Event eventToRemove) {
eventList.removeParticipant(personToRemove); //Maintain participantList in Event
personList.removeParticipant(eventToRemove); //Maintain participationList in Person
// Save changes to the storage.
}
PersonList
and EventList
will then locate the person/event-to-remove in its list, and perform deletion.
Connecting a person to an event: join
Similar to disjoin
command, join
event follows the same mechanism. This command will call ModelManager
’s joinEvent() method to connect the person and the event, as shown in the following code segment. Subsequently, PersonList
and EventList
will then locate the person/event-to-add in its list, and perform addition.
public void join(Person personToAdd, Event eventToAdd) {
eventList.addParticipant(personToAdd); //Maintain participantList in Event
personList.addParticipant(eventToAdd); //Maintain participationList in Person
// Save changes to the storage.
}
The portrait command is facilitated by LogicManager
and is extended from UndoableCommand
.
The following diagram (Figure 4.12.1) shows the structure of PortraitPath class:
Figure 4.12.1 PortraitPath class structure
As shown in above diagram, we only store a string value, which is the file path in this class. We load the image file in the UI component.
The portrait can only be changed by this command. In other words, Add
command can only create a person without a portrait.
However, users may type a wrong path. As we only load it in the UI component, we will only know if any errors occur when it reaches the UI component, which makes handling exceptions harder as they are usually handled in the Logic component. Therefore, we apply defensive coding here. When the app knows that it cannot load the file by this path, a default picture will be loaded. The following code shows it:
if (filePath.isEmpty() || !new File(filePath).exists()) {
url = PortraitPath.DEFAULT_PORTRAIT_PATH;
} else {
url = PortraitPath.FILE_PREFIX + filePath;
}
Image portrait = new Image(url);
Aspect: How to store the portrait.
Alternative 1 (current choice): Only store the path , and only load the image in UI.
Pros: This is easy to implement, and this uses less memory.
Cons: User cannot move the image file in the computer, otherwise the user needs to change the path.
Alternative 2: Store the image file.
Pros: The app does not need to load the image every time.
Cons: It is hard to store images into .xml file, and consumes a lot of memory.
The toggle mechanism is an event-driven mechanism. The following diagram (Figure 4.13.1) below shows the overview of the high-level interactions between components for the toggle mechanism:
Figure 4.13.1 Toggle mechanism component interactions
The following sequence diagram (Figure 4.13.2) below shows how the first half of the toggle mechanism works up till the posting of the event:
Figure 4.13.2 Toggle mechanism sequence 1
As seen in the sequence diagram (Figure 4.13.2) above, when the user enters a toggle command, a ToggleCommand
is created.
When the LogicManager
executes the ToggleCommand
, the EventsCenter
will post a TogglePanelEvent
to the EventBus
which is shown in the code snippet below:
public class ToggleCommand extends Command {
@Override
public CommandResult execute() throws CommandException {
EventsCenter.getInstance().post(new TogglePanelEvent());
// ... Return CommandResult ...
}
}
The following sequence diagram (Figure 4.13.3) below shows the second half of the toggle mechanism starting with the handling of the posted event:
Figure 4.13.3 Toggle mechanism sequence 2
As seen in the sequence diagram (Figure 4.13.3) above, the event is handled by the TogglePanel
.
The TogglePanel
has a variable called browserIsFront
which keeps track of whether the browser is currently displayed.
TriggerToggle
uses browserIsFront
to toggle the correct panel to the front and updates browserToFront
accordingly.
This is shown in the code snippet below:
public class TogglePanel extends UiPart<Region> {
@Subscribe
private void handleTogglePanelEvent(TogglePanelEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
triggerToggle();
}
private void triggerToggle() {
if (browserIsFront) {
browserToBack();
} else {
browserToFront();
}
}
private void browserToFront() {
browserPlaceHolder.setVisible(true);
browserPlaceHolder.toFront();
toggleSplitPane.setVisible(false);
toggleSplitPane.toBack();
browserIsFront = true;
}
private void browserToBack() {
browserPlaceHolder.setVisible(false);
browserPlaceHolder.toBack();
toggleSplitPane.setVisible(true);
toggleSplitPane.toFront();
browserIsFront = false;
}
}
As seen from the above code snippet, when browserIsFront
is true, the place holder where the browser is at, will be sent to the back and made to be invisible. While the toggleSplitPane
, which holds the information board and events list, is brought to the front and made visible. BrowserIsFront
is then set to be false.
When browserIsFront
is false, the opposite happens. The place holder with the browser is brought to the front and made visible, while the toggleSplitPane
is sent to the back and made to be invisible.
One thing to take note of with the select command, is that the browser will be brought to the front and displayed regardless of the current status of browserIsFront
.
This is shown in the code snippet below:
public class BrowserPanel extends UiPart<Region> {
@Subscribe
private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
loadPersonPage(event.getNewSelection().person);
raise(new ToggleSelectEvent());
}
}
public class TogglePanel extends UiPart<Region> {
@Subscribe
private void handleToggleSelectEvent(ToggleSelectEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
browserToFront();
}
}
As seen from the above code snippet, the BrowserPanel
handles the event posted whenever the select command is executed.
It does so by raising a ToggleSelectEvent
, which the TogglePanel
handles by calling browserToFront
method which will set the browser to be at the front.
Aspect: How to implement the toggle mechanism.
Alternative 1 (current choice): Use a toggle panel to hold the containers of the browser, information board and events list.
Pros: Easy to implement. Visibility depends on the containers, any modification will not touch the code of the actual panels much if at all.
Cons: Will need to adjust the FXML file when changes are made to what is toggleable.
Alternative 2: Have each toggleable panel know how to toggle.
Pros: No need change the FXML file when changes are made to what is toggleable.
Cons: Will have to make large changes to the actual panels themselves.
The find event mechanism is facilitated by EventNameContainsKeyWordPredicate
class, which resides in model.event
package. This command
supports users of finding a list of events by their names. Any event that has either of the entered keyword will be filtered
into the list. This command inherits from Command
.
Below is a sequence diagram (Figure 4.14) for executing a find event command: findE first
. It will find events which contain keyword first
in their names.
Figure 4.14: sequence diagram for find event command
When users enters a findE first
, LogicManager
will receive it and call AddressBookParser
to parse command. In the AddressBookParser
, it will create FindEventParser
to parse first
.
Then FindEventCommandParser
will parse argument first
and create a new FindEventCommand
object with EventNameContainsKeywordPredicate
which is p
in the diagram:
public FindEventCommand parse(String args) throws ParseException {
String trimmedArgs = args.trim();
if (trimmedArgs.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindEventCommand.MESSAGE_USAGE));
}
String[] nameKeywords = trimmedArgs.split("\\s+");
return new FindEventCommand(new EventNameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
}
FindEventCommand
will use the predicate to update the event list by calling updateFilteredEventList(p)
method in the Model
.
And This is how EventNameContainsPredicate
filters the event list:
@Override
public boolean test(ReadOnlyEvent event) {
return keywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(event.getEventName().fullEventName, keyword));
}
After Model
updates the event list which will be shown in the GUI, the FindEventCommand
object will create CommandResult
object and returns it. And LogicManager
receives CommandResult
and shows related message through UI
.
Aspect: Implementation of find event command
Alternative 1 (current choice): Select events that have any of the keywords in their names
Pros: It is easier for users to find events without knowing their full names.
Cons: Users may get more unwanted results in the event list.
Alternative 2: Select events that have all the keywords in their names
Pros: Users can find the event they want accurately.
Cons: Sometimes it is hard for users to remember the full name of an event.
The sort event command is facilitated by the LogicManager
class. It supports sorting the event list according to their dates so that users can decide to process which event first.
The following sequence diagram (Figure 4.15) shows how the sort event command works:
Figure 4.15: sequence diagram for sort event command
When user enters a sort event command, it will be received by LogicManager
.Then, LogicManager
calls AddressBookParser
to parse the command. And AddressBookParser
will create SortEventCommand
command object and returns it.When LogicManager
receives the command object, it will execute it.
The SortEventCommand
object calls sortEvents()
in the `Model`as follow:
public class SortEventCommand extends Command {
public static final String COMMAND_WORD = "sortE";
public static final String MESSAGE_SUCCESS = "Sorted all events";
@Override
public CommandResult execute() {
model.sortEvents();
return new CommandResult(MESSAGE_SUCCESS);
}
}
And This is how Model
update the event list using sortEvents()
:
public synchronized void sortEvents() {
eventList.sortEvents();
updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
}
In the event list, we will sort events according to their dates;
public void sort() {
Collections.sort(internalList, new Comparator<Event>() {
public int compare (Event p1, Event p2) {
return p1.getEventTime().orderForSort().compareTo(p2.getEventTime().orderForSort()); } });
}
After sorting event list, SortEventCommand
will create CommandResult
object and return it. And LogicManager
receives CommandResult
and shows related message through UI
.
Aspect: How sort event command affects the data in the event list
Alternative 1 (current choice): Sort the copy of event list and return it
Pros: It is more defensive and keeps the data unchanged.
Cons: Users have to sort the list every time when they open the application.
Alternative 2: Sort the actual event list and return it
Pros: Users do not need to type the command every time.
Cons: Sort event command is not undoable which means that the order of list cannot change anymore after you call it. It is also less defensive because you can frequently change the original data.
Aspect: How sort event command executes for the event list+
Alternative 1 (current choice): Sort the event list according to ascending order of their dates
Pros: It is easy to implement.
Cons: Users do not have other choices to sort the event list.
Alternative 2: Sort the event list according to different order (ascending order or descending order)
Pros: Users will have more choices.
Cons: It will be more complex for developers to maintain and test.
We are using java.util.logging
package for logging. The LogsCenter
class is used to manage the logging levels and logging destinations.
-
The logging level can be controlled using the
logLevel
setting in the configuration file (See Configuration) -
The
Logger
for a class can be obtained usingLogsCenter.getLogger(Class)
which will log messages according to the specified logging level -
Currently log messages are output through:
Console
and to a.log
file.
Logging Levels
-
SEVERE
: Critical problem detected which may possibly cause the termination of the application -
WARNING
: Can continue, but with caution -
INFO
: Information showing the noteworthy actions by the App -
FINE
: Details that are not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
We use asciidoc for writing documentation.
ℹ️
|
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. |
See UsingGradle.adoc to learn how to render .adoc
files locally to preview the end result of your edits.
Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc
files in real-time.
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot (Figure 5.3) below.
Figure 5.3: Saving documentation as PDF files in Chrome
There are three ways to run tests.
💡
|
The most reliable way to run tests is Method 3 . The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.
|
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean allTests
(Mac/Linux:./gradlew clean allTests
)
ℹ️
|
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux: ./gradlew clean headless allTests
)
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include:
-
System Tests that test the entire App by simulating user actions on the GUI. They are in the
systemtests
package. -
Unit tests that test the individual components. They are in
seedu.address.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include:
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.address.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.address.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.address.logic.LogicManagerTest
-
See UsingGradle.adoc to learn how to use Gradle for build automation.
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
Here are the steps to create a new release:
-
Update the version number in
MainApp.java
. -
Generate a JAR file using Gradle.
-
Tag the repo with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
A project often depends on third-party libraries. For example, Planno depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:
a. Including those libraries in the repo (this bloats the repo size)
b. Requiring developers to download those libraries manually (this creates extra work for developers)
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
user |
add a person to an event |
keep track of who is involved |
|
user |
delete a person from an event |
remove a person who is no longer participating |
|
user |
list all events a person is involved in |
easily check which events a person is a participant of |
|
user |
list every person involved in an event |
easily check who is participating |
|
new user |
see usage instructions |
refer to instructions when I forget how to use the App |
|
user |
add a new person |
add a person’s contact detail into the app |
|
user |
add a new event |
add an event’s date and information into the app |
|
user |
delete a person or event |
remove entries that I no longer need |
|
user |
edit a person or event |
change some information of the person or event |
|
user |
clear my address book |
refresh it quickly |
|
user with many friends |
list all friends with a certain tag |
group my friend by tag easily |
|
user |
add a person event with blank information |
add him or the event even if I do not know some details (e.g his address) |
|
user |
find a person by name and tag |
locate details of persons without having to go through the entire list |
|
user |
get help information |
know where is wrong when I get errors |
|
user |
list contacts |
view who are my contacts |
|
user |
list events |
view what are my events |
|
secretive user |
hide private details |
minimise the chance of someone else seeing them by accident |
|
careless user |
undo decisions |
revert changes in case of mistake |
|
user |
toggle between the events details and browser |
use a browser without having to alternate between many applications |
|
frequent user |
change the font and background colour |
use address book comfortably |
|
frequent user |
use non-case sensitive commands |
type commands easily |
|
user |
sort contacts |
view my list easily |
|
user |
tag my contacts |
remember who they are through tags |
|
secretive user |
hide private contact details by default |
minimize chance of someone else seeing them by accident |
|
forgetful user |
stick some important people on the top |
locate them quickly |
|
user |
create filter using multiple tags |
make a specific search using tags |
|
user |
add pictures to contacts |
remember who they are through pictures |
{More to be added}
(For all use cases below, the System is the Planno
and the Actor is the user
, unless specified otherwise)
MSS
-
User enters values for a new person.
-
Planno adds the new person into the database.
Use case ends.
Extensions
-
2a. There is already this person.
-
Planno shows a person already exists message.
Use case ends.
-
-
2b. User entered invalid values.
-
Planno shows an invalid values message.
Use case resumes at step 1.
-
-
2b. The list is empty.
Use case ends.
MSS
-
User enters values for a new event.
-
Planno adds the new event into the database.
Use case ends.
Extensions
-
2a. There is already this event.
-
Planno shows an event already exists message.
Use case ends.
-
-
2b. User entered invalid values.
-
Planno shows an invalid values message.
Use case resumes at step 1.
-
-
2b. The list is empty.
Use case ends.
MSS
-
User requests to list persons.
-
Planno shows a list of persons.
-
User requests to delete a specific person in the list.
-
Planno deletes the person.
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
Planno shows an error message.
Use case resumes at step 2.
-
-
4a. The person is a participant of an event.
-
Planno shows a person is participating in an event message.
Use case resumes at step 2.
-
MSS
-
User requests to list events.
-
Planno shows a list of events.
-
User requests to delete a specific event in the list.
-
Planno deletes the event.
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
Planno shows an error message.
Use case resumes at step 2.
-
-
4a. The event has a participant/s.
-
Planno shows an event has participants message.
Use case resumes at step 2.
-
MSS
-
User finds target person by his name.
-
Planno shows the result of find command.
-
User types new information for the target person.
-
Planno updates the information and displays successful message.
Use case ends.
Extensions
-
2a. The target person cannot be found.
Use case ends.
-
3a. User types invalid information.
-
Planno shows an error message.
Use case resumes at step 2.
-
-
3b. New information is the same as original.
-
Planno shows an unnecessary update message.
Use case ends.
-
MSS
-
User finds target event by name.
-
Planno shows the result of findE command.
-
User types new information for the target event.
-
Planno updates the information and displays successful message.
Use case ends.
Extensions
-
2a. The target event cannot be found.
Use case ends.
-
3a. User types invalid information.
-
Planno shows an error message.
Use case resumes at step 2.
-
-
3b. New information is the same as original.
-
Planno shows an unnecessary update message.
Use case ends.
-
MSS
-
User enters values for a new person including tag details.
-
Planno adds the new person into the database.
Use case ends.
Extensions
-
2a. There is already this person.
-
Planno shows a person already exists message.
Use case ends.
-
-
2b. The list is empty.
Use case ends.
MSS
-
User enters "list" command.
-
Planno displays list of persons.
Use case ends.
Extensions
-
1a. There is no person in Planno.
-
Planno shows a successful message.
Use case ends.
-
MSS
-
User enters "listE" command.
-
Planno displays list of persons.
Use case ends.
Extensions
-
1a. There is no event in EventList.
-
Planno shows a successful message.
Use case ends.
-
MSS
-
User enters "sort" command.
-
Planno shows a list of sorted persons.
Use case ends.
Extensions
-
2a. The list is empty.
-
Planno shows a successful message.
Use case ends.
-
MSS
-
User enters values for find command.
-
Planno displays persons with name matching at least one keyword.
Use case ends.
Extensions
-
1a. There is no person with a name matching any keyword.
-
Planno shows a blank person list.
Use case ends.
-
MSS
-
User enters tag names for a person list.
-
Planno displays the list of persons that contains any of the tag names.
Use case ends.
Extensions
-
1a. User does not enter tag names.
-
Planno shows an error message.
Use case ends.
-
-
1b. User does not use correct format.
-
Planno displays an empty list.
Use case ends.
-
MSS
-
User enters values for find command.
-
Planno displays events with name matching at least one keyword.
Use case ends.
Extensions
-
1a. There is no event with a name matching any keyword.
-
Planno shows a blank event list.
Use case ends.
-
MSS
-
User enters value for list command.
-
Planno displays list of persons.
-
User enters index value for select command.
-
Planno displays Google search page of the person at the entered index value.
Use case ends.
Extensions
-
1a. There is no person in Planno.
-
Planno shows an error message.
Use case ends.
-
-
1b. User enters value for find command.
-
Planno displays persons with name matching at least one keyword.
Use case resumes at step 3.
-
-
3a. User enters invalid index value.
-
Planno displays an invalid index message.
Use case ends.
-
MSS
-
User enters value for toggle command.
-
Planno switches the display from information board to browser if the former is currently displayed (vice versa).
Use case ends.
Extensions
-
1a. User enters a select command.
-
Planno displays the browser.
Use case ends.
-
MSS
-
User enters value for list command.
-
Planno displays list of persons.
-
User enters value for list event command.
-
Planno displays list of events.
-
User enters index value/s of the person list and event list for join command.
-
Planno adds the person as a participant of the event.
Use case ends.
Extensions
-
1a. There is no person in Planno.
-
Planno shows a blank person list.
Use case ends.
-
-
1b. User enters value for find command.
-
Planno displays persons with name matching at least one keyword.
Use case resumes at step 3.
-
-
5a. User enters invalid index value.
-
Planno displays an invalid index message.
Use case ends.
-
-
5b. Person has already joined the event.
-
Planno displays an already joined message.
Use case ends.
-
MSS
-
User enters value for list command.
-
Planno displays list of persons.
-
User enters value for list event command.
-
Planno displays list of events.
-
User enters index value/s of the person list and event list for disjoin command.
-
Planno removes the person as a participant of the event.
Use case ends.
Extensions
-
1a. There is no person in Planno.
-
Planno shows a blank person list.
Use case ends.
-
-
1b. User enters value for find command.
-
Planno displays persons with name matching at least one keyword.
Use case resumes at step 3.
-
-
5a. User enters invalid index value.
-
Planno displays an invalid index message.
Use case ends.
-
-
5b. Person is not a participant of the event.
-
Planno displays a person does not participate in this event message.
Use case ends.
-
MSS
-
User requests to list events.
-
Planno shows a list of events.
-
User requests to show participants of a specific event in the list.
-
Planno shows participants of the event.
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
Planno shows an error message.
Use case resumes at step 2.
-
MSS
-
User enters value for list command.
-
Planno displays list of persons.
-
User enters value for list event command.
-
Planno displays list of events.
-
User enters index value/s of the person list for selectE command.
-
Planno displays all participated events of the person/s at the entered index value/s.
Use case ends.
Extensions
-
1a. There is no person in Planno.
-
Planno shows a blank person list.
Use case ends.
-
-
1b. User enters value for find command.
-
Planno displays persons with name matching at least one keyword.
Use case resumes at step 3.
-
-
3a. There is no event in Planno.
-
Planno displays a blank event list.
Use case resumes at step 5.
-
-
5a. User enters invalid index value.
-
Planno displays an invalid index message.
Use case ends.
-
-
5b. The selected person has not joined any events.
-
Planno displays a blank event list.
Use case ends.
-
MSS
-
User enters "clear" command to delete all entries in Planno.
-
Planno deletes all data.
Use case ends.
MSS
-
User enters "history" command to view entire history of commands used.
-
Planno displays entire history of commands used.
-
User enters "undo" to undo the last undoable command.
-
Planno undoes the last undoable command and displays successful message.
Use case ends.
Extensions
-
3a. No more undoable command commands in the history.
-
Planno displays error message.
Use case ends.
-
MSS
-
User enters "history" command to view entire history of commands used.
-
Planno displays entire history of commands used.
-
User enters "redo" to request to redo the last redoable command.
-
Planno redoes the last redoable command and displays successful message.
Use case ends.
Extensions
-
3a. No more redoable command commands in the history.
-
Planno displays error message.
Use case ends.
-
MSS
-
User enters value for help command.
-
Planno displays the help window.
Use case ends.
Extensions
-
1a. User presses F1 on the keyboard or clicks on help icon.
Use case resumes at step 2.
MSS
-
User enters value for history command.
-
Planno displays a list of commands the User has entered from the most recent to earliest.
Use case ends.
Extensions
-
1a. User has not entered any previous commands.
-
Planno displays a no previous command entered message.
Use case ends.
-
MSS
-
User enters "exit" command for exiting the app.
-
Planno terminates its work and exits.
Use case ends.
Extensions
-
1a. User clicks on File → Exit button.
Use case resumes at step 2.
-
The application should work on any mainstream OS as long as it has Java
1.8.0_60
or higher installed. -
The application should be able to hold up to 1000 persons and events without a noticeable sluggishness in performance for typical usage.
-
A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-
The application should be able to give feedback messages within 2 seconds for every command.
-
The user should be able to use the application without connecting to Internet.
-
The user should be able to use the application without any programming knowledge background.
-
The size of this application should not exceed 10GB.
-
The application should work on both 32-bit and 64-bit environments.
-
The cost of this application should not exceed 10,000 dollars.
-
The application should protect users' privacy.
-
Font size should be suitable for people aged 6 to 70 years old.
-
The application should not tolerate data loss.
-
The application should be usable to a novice who has never used such types of application before.
-
The application should startup within 7 seconds
-
Background color should be appropriate to not discomfort users' eyes.
{More to be added}
API
An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.
Blank information
An information field with null value. It may happen when a user adds a person and does not know some information.
Build automation
Build automation is the process of automating the creation of a software build and the associated processes.
CI
Continuous integration(CI) is an extreme application of build automation in which integration, building, and testing happens automatically after each code change.
Gradle
Gradle is an open source build automation system that automates the creation of a software build.
Logging
Logging is the deliberate recording of certain information during a program execution for future reference. It can be useful for troubleshooting problems.
Mainstream OS
Windows, Linux, Unix, OS-X.
Private contact detail
A contact detail that is not meant to be shared with others.
Redoable command
A redoable command is a command which has been undone, and no other commands in between.
Travis
Travis CI is a hosted, distributed continuous integration service used to build and test projects hosted at GitHub.
Undoable command
An undoable command is a command which modifies data in Planno. For example, add, delete are undoable commands, while find and list are not.